﻿using FastApi.Attributes;
using FastEmit;
using Microsoft.Owin;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace FastApi
{
    internal class FastUtils
    {
        #region private method

        private static bool IsSimpleType(Type type)
        {
            if (type == typeof(string) || type == typeof(char) || type == typeof(char?))
            {
                return true;
            }
            if (type == typeof(int) || type == typeof(int?) || type == typeof(uint) || type == typeof(uint?))
            {
                return true;
            }
            if (type == typeof(long) || type == typeof(long?) || type == typeof(ulong) || type == typeof(ulong?))
            {
                return true;
            }
            if (type == typeof(DateTime) || type == typeof(DateTime?))
            {
                return true;
            }
            if (type == typeof(bool) || type == typeof(bool?))
            {
                return true;
            }
            if (type == typeof(decimal) || type == typeof(decimal?))
            {
                return true;
            }
            if (type == typeof(double) || type == typeof(double?))
            {
                return true;
            }
            if (type == typeof(float) || type == typeof(float?))
            {
                return true;
            }
            if (type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?))
            {
                return true;
            }
            if (type == typeof(short) || type == typeof(short?) || type == typeof(ushort) || type == typeof(ushort?))
            {
                return true;
            }
            if (type == typeof(byte) || type == typeof(byte?) || type == typeof(sbyte) || type == typeof(sbyte?))
            {
                return true;
            }
            if (type == typeof(Enum))
            {
                return true;
            }
            if (type == typeof(Guid) || type == typeof(Guid?))
            {
                return true;
            }
            if (type == typeof(TimeSpan) || type == typeof(TimeSpan?))
            {
                return true;
            }
            return false;
        }

        private static object GetTypeDefaultVal(Type type)
        {
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                return null;
            }
            //return type.IsValueType ? Activator.CreateInstance(type) : null;
            return ConstructorExtensions.CreateInstance(type);
        }

        private static List<FastParam> CreateFastParList(ParameterInfo[] parArray, Dictionary<RuntimeTypeHandle, FastParamCreator> dict, string method = "GET")
        {
            var list = new List<FastParam>();
            foreach (var item in parArray)
            {
                dict.TryGetValue(item.ParameterType.TypeHandle, out var creator);

                var isSimple = IsSimpleType(item.ParameterType);

                var attrs = item.GetCustomAttributes();

                var parType = FastParamType.Query;

                if (attrs.Any(a => a is FastBodyAttribute))
                {
                    parType = FastParamType.Body;
                }
                else if (attrs.Any(a => a is FastFormAttribute))
                {
                    parType = FastParamType.Form;
                }
                else if (method == "POST")
                {
                    parType = FastParamType.Body;
                }

                var model = new FastParam(item.ParameterType, item.Name, isSimple, parType, creator);

                if (attrs.Any())
                {
                    model.AttributeList.AddRange(attrs);
                }

                list.Add(model);
            }
            return list;
        }

        internal static async Task<object[]> CreateParamList(FastContext fastContext, int flag)
        {
            List<FastParam> parList;
            if (flag == 0)
            {
                parList = fastContext.Module.ParamList;
            }
            else
            {
                parList = fastContext.Action.ParamList;
            }

            var context = fastContext.HttpContext;
            var objArray = new object[parList.Count];

            IFormCollection form = fastContext.Form;
            string jsonString = null;
            if (fastContext.Action.HttpMethod == "POST")
            {
                var contentType = context.Request.ContentType;
                if (contentType == "application/x-www-form-urlencoded" || contentType.StartsWith("multipart/form-data")) //form
                {
                    if (form == null)
                    {
                        form = await context.Request.ReadFormAsync();
                        fastContext.Form = form;
                    }
                }
                else
                {
                    if (string.IsNullOrEmpty(jsonString))
                    {
                        using var stream = new StreamReader(context.Request.Body, Encoding.UTF8);
                        jsonString = await stream.ReadToEndAsync();
                    }
                }
            }

            for (int i = 0; i < parList.Count; i++)
            {
                var item = parList[i];

                if (item.IsSimpleType)
                {
                    string val;
                    if (item.RequestType == FastParamType.Query)
                    {
                        val = context.Request.Query[item.Name];
                    }
                    else if (item.RequestType == FastParamType.Form)
                    {
                        val = form[item.Name];
                    }
                    else //body
                    {
                        val = jsonString;
                    }

                    if (item.Type == typeof(string))
                    {
                        objArray[i] = val;
                    }
                    else
                    {
                        if (string.IsNullOrEmpty(val))
                        {
                            objArray[i] = GetTypeDefaultVal(item.Type);
                        }
                        else
                        {
                            objArray[i] = TypeConverter.Get(item.Type, val);
                        }
                    }
                }
                else if (item.IsCustomerType)
                {
                    if (item.Creator != null)
                    {
                        if (item.Creator.Create != null)
                        {
                            objArray[i] = item.Creator.Create.Invoke(fastContext);
                        }
                        else
                        {
                            objArray[i] = await item.Creator.CreateAsync.Invoke(fastContext);
                        }
                    }
                    else if (item.Type == typeof(IFastHttp))
                    {
                        objArray[i] = new FastHttp(fastContext, fastContext.UserData);
                    }
                    else if (item.Type == typeof(IOwinContext))
                    {
                        objArray[i] = context;
                    }
                    else if (item.Type == typeof(IOwinRequest))
                    {
                        objArray[i] = context.Request;
                    }
                    else if (item.Type == typeof(IOwinResponse))
                    {
                        objArray[i] = context.Response;
                    }
                }
                else //复杂类型
                {
                    if (item.RequestType == FastParamType.Query)
                    {
                        objArray[i] = context.Request.Query.BindModel(item.Type);
                    }
                    else if (item.RequestType == FastParamType.Form)
                    {
                        objArray[i] = form.BindModel(item.Type);
                    }
                    else //body
                    {
                        objArray[i] = JsonConvert.DeserializeObject(jsonString, item.Type);
                    }
                }
            }
            return objArray;
        }

        #endregion

        internal static void AddType(string route, Type type, FastApp app)
        {
            if (app.FastModuleDict.ContainsKey(route))
            {
                throw new Exception($"route:{route} exists.");
            }

            #region 类处理

            var classInfo = ConstructorExtensions.Constructors(type).First();
            var classInvoker = ConstructorInfoExtensions.DelegateForCreateInstance(classInfo);
            var classParArray = classInfo.GetParameters();
            var classParList = CreateFastParList(classParArray, app.ParamCreatorDict);

            var ddd = type.CustomAttributes;

            //获取类特性
            var classAttrs = type.GetCustomAttributes(false).Where(w =>
                                  w.GetType().Name != "NullableContextAttribute" &&
                                  w.GetType().Name != "NullableAttribute"
                                ).ToArray();

            var classClear = false;
            var classLogTime = false;

            if (classAttrs.Any(a => a is FastFilterClearAttribute))
            {
                classClear = true;
            }

            if (classAttrs.Any(a => a is FastLogTimeAttribute))
            {
                classLogTime = true;
            }

            var fastModule = new FastModule(type, classInvoker, route, classClear, classLogTime);
            if (classParList.Any())
            {
                fastModule.ParamList.AddRange(classParList);

                if (classParList.Any(a => a.NeedDispose))
                {
                    fastModule.NeedDispose = true;
                }

                if (classParList.Any(a => a.IsTran))
                {
                    fastModule.IsTran = true;
                }
            }

            if (classAttrs.Any())
            {
                fastModule.AttributeList.AddRange(classAttrs);
            }

            #endregion

            //获取所有方法
            var methods = type.GetMethods(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Instance);
            foreach (var item in methods)
            {
                //跳过 get_ 和set_等特殊方法
                if (item.IsSpecialName)
                {
                    continue;
                }

                //获取方法特性
                var methodAttrs = item.GetCustomAttributes(true).Where(w =>
                                  w.GetType().Name != "NullableContextAttribute" &&
                                  w.GetType().Name != "AsyncStateMachineAttribute"
                                ).ToArray();

                //路由忽略，直接跳过
                if (methodAttrs.Any(a => a is FastIgnoreAttribute))
                {
                    continue;
                }

                var httpMethod = "GET";
                if (methodAttrs.Any(a => a is FastPostAttribute))
                {
                    httpMethod = "POST";
                }

                #region 参数处理

                var parArray = item.GetParameters();
                var parList = CreateFastParList(parArray, app.ParamCreatorDict, httpMethod);

                //参数存在Body或者Form 则判定为POST
                if (parList.Any(a => a.RequestType == FastParamType.Body || a.RequestType == FastParamType.Form))
                {
                    httpMethod = "POST";
                }

                #endregion

                #region 方法处理

                var logTime = false;
                var clearFilter = false;

                if (methodAttrs.Any(a => a is FastLogTimeAttribute))
                {
                    logTime = true;
                }

                if (methodAttrs.Any(a => a is FastFilterClearAttribute))
                {
                    clearFilter = true;
                }

                var returnType = item.ReturnType;
                FastActionType actionType = FastActionType.Default;
                FastActionTypeTask actionTypeTask = FastActionTypeTask.Default;

                #region FastFile

                if (returnType == typeof(FastFile))
                {
                    actionType = FastActionType.File;
                }

                if (returnType == typeof(Task<FastFile>))
                {
                    actionType = FastActionType.File;
                    actionTypeTask = FastActionTypeTask.TaskT;
                }

                #endregion

                #region FastRedirect

                if (methodAttrs.Any(a => a is FastRedirectAttribute))
                {
                    if (returnType == typeof(string))
                    {
                        actionType = FastActionType.Redirect;
                    }

                    if (returnType == typeof(Task<string>))
                    {
                        actionType = FastActionType.Redirect;
                        actionTypeTask = FastActionTypeTask.TaskT;
                    }
                }

                #endregion

                #region FastView

                if (returnType == typeof(FastView))
                {
                    actionType = FastActionType.View;
                }

                if (returnType == typeof(Task<FastView>))
                {
                    actionType = FastActionType.View;
                    actionTypeTask = FastActionTypeTask.TaskT;
                }

                #endregion

                #region Customer

                if (methodAttrs.Any(a => a is FastCustomerAttribute))
                {
                    actionType = FastActionType.Customer;
                }

                #endregion

                #region Task

                if (returnType == typeof(Task))
                {
                    actionTypeTask = FastActionTypeTask.Task;
                }
                else if (returnType.FullName.StartsWith("System.Threading.Tasks.Task"))
                {
                    actionTypeTask = FastActionTypeTask.TaskT;
                }

                #endregion


                var routeName = item.Name;
                var fastRoute = methodAttrs.FirstOrDefault(a => a is FastRouteAttribute) as FastRouteAttribute;
                if (fastRoute != null)
                {
                    routeName = fastRoute.Name;
                }
                else
                {
                    routeName = char.ToLower(routeName[0]) + routeName.Substring(1);
                    //async路由处理
                    if (actionTypeTask != FastActionTypeTask.Default && routeName.Length > 5)
                    {
                        if (routeName.EndsWith("Async"))
                        {
                            routeName = routeName.Substring(0, routeName.Length - 5);
                        }
                    }
                }

                var methodInvoker = FastEmit.MethodInfoExtensions.DelegateForCallMethod(item);

                var action = new FastAction(httpMethod, routeName, item.Name, clearFilter, methodInvoker,
                    item.IsStatic, returnType, actionType, actionTypeTask, logTime);

                if (parList.Any())
                {
                    action.ParamList.AddRange(parList);

                    if (parList.Any(a => a.NeedDispose))
                    {
                        action.NeedDispose = true;
                    }

                    if (parList.Any(a => a.IsTran))
                    {
                        action.IsTran = true;
                    }
                }

                #endregion

                fastModule.ActionDict.Add(routeName.ToLower(), action);
            }

            app.FastModuleDict.Add(route, fastModule);
        }

    }
}
